#include <cpcap-capture-devices.hpp>
#include <stdlib.h>

#include <common.hpp>

#ifdef LINUX
#include <pthread.h>
#else
#include <windows.h>
#include <mdshow.hpp>
#include <sample-grabber.hpp>
#endif

#ifdef LINUX
#include <linux-fcn.hpp>
#endif

#ifdef WIN32
#include <sample-grabber.hpp>
#define PPVOID(x) reinterpret_cast<LPVOID *>(&x)

CPCapDevice::CPCapDevice(const char *_uid,
            const char *_name,
            int pC,
            int w,
            int h,
            int fmt,
            QObject *parent) :
    QObject(parent)
{
    /* FIXME: cambiar a lista de inicializacion. */
    CoInitialize(NULL);

    connected           = false;
    pGraph              = NULL;
    pControl            = NULL;
    pCap                = NULL;
    pGrabber            = NULL;
    pCrossbar           = NULL;
    sgCallback          = NULL;
    nullRenderer        = NULL;
    pBuild              = NULL;
    physical_connection = pC;
    capture_mode        = PIN_CATEGORY_CAPTURE;
    deviceID            = mstrdup(_uid);
    deviceName          = mstrdup(_name);

    Q_UNUSED(w)
    Q_UNUSED(h)
    Q_UNUSED(fmt)
    Q_UNUSED(w)
}

CPCapDevice::CPCapDevice(const char *_uid, const char *_name, int pC, QObject *parent) :
    QObject(parent)
{
    /* FIXME: cambiar a lista de inicializacion. */
    CoInitialize(NULL);

    connected           = false;
    pGraph              = NULL;
    pControl            = NULL;
    pCap                = NULL;
    pGrabber            = NULL;
    pCrossbar           = NULL;
    sgCallback          = NULL;
    nullRenderer        = NULL;
    pBuild              = NULL;
    physical_connection = pC;
    capture_mode        = PIN_CATEGORY_CAPTURE;
    deviceID            = mstrdup(_uid);
    deviceName          = mstrdup(_name);
}

CPCapDevice::~CPCapDevice()
{
    cleanUp();

    if (deviceID)
        delete[] deviceID;
    if (deviceName)
        delete [] deviceName;

    CoUninitialize();
}

void
CPCapDevice::cleanUp()
{
    HRESULT       result;
    IEnumFilters  *pEnumMoniker = NULL;
    unsigned long fetched = 0;
    IBaseFilter   *filter = NULL;

    if (pControl) {
        pControl->StopWhenReady();
        pControl->Release();
        pControl = NULL;
    }

    /* Eliminar el pGraph (Completo) */
    if (pGraph) {
        if (pCap) {
            pGraph->RemoveFilter(pCap);
            pCap->Release();
            pCap = NULL;
        }
        if (pGrabber) {
            pGraph->RemoveFilter(pGrabber);
            pGrabber->Release();
            pGrabber = NULL;
        }
        if (nullRenderer) {
            pGraph->RemoveFilter(nullRenderer);
            nullRenderer->Release();
            nullRenderer = NULL;
        }
        result = pGraph->EnumFilters(&pEnumMoniker);
        if (SUCCEEDED(result) && pEnumMoniker) {
            while (pEnumMoniker->Next(1, &filter, &fetched) == S_OK) {
                if (fetched == 1 && filter) {
                    pGraph->RemoveFilter(filter);
                    filter->Release();
                    filter = NULL;
                }
            }
            pEnumMoniker->Release();
        }
        /* Eliminar el Resto */
        pGraph->Release();
        pGraph = NULL;
    }
    if (sgCallback) {
        sgCallback->Release();
        sgCallback = NULL;
    }

    /* Por si las moscas */
    connected = false;
}

HRESULT
CPCapDevice::insertFilter(const TCHAR *str, IBaseFilter **fltr, CLSID id)
{
    HRESULT result;
    TCHAR 	filterSTR[128] = {'\0'};

    swprintf_s(filterSTR, 128, L"%s_%sFilter", deviceName, str);

    result = CoCreateInstance(id,
                              0,
                              CLSCTX_INPROC_SERVER,
                              IID_IBaseFilter,
                              PPVOID(fltr));
    if (FAILED(result))
        return result;

    return pGraph->AddFilter(*fltr, (WCHAR *)filterSTR);
}

void
CPCapDevice::connectDevice()
{
    HRESULT      result;
    IMediaFilter *pMediaFilter = NULL;

    result = CoCreateInstance(CLSID_FilterGraph,
                              0,
                              CLSCTX_INPROC_SERVER,
                              IID_IGraphBuilder,
                              PPVOID(pGraph));
    if (FAILED(result))
        goto abort;

    result = CoCreateInstance(CLSID_CaptureGraphBuilder2,
                              0,
                              CLSCTX_INPROC_SERVER,
                              IID_ICaptureGraphBuilder2,
                              PPVOID(pBuild));
    if (FAILED(result))
        goto abort;

    result = pBuild->SetFiltergraph(pGraph);
    if (FAILED(result))
        goto abort;

    result = getDeviceFilter();
    if (FAILED(result))
        goto abort;

    result = insertFilter(L"NullRenderer",
                          &nullRenderer,
                          CLSID_NullRenderer);
    if (FAILED(result))
        goto abort;

    result = insertFilter(L"SampleGrabber",
                          &pGrabber,
                          CLSID_SampleGrabber);
    if (FAILED(result))
        goto abort;

    result = pGrabber->QueryInterface(IID_ISampleGrabber,
                                      PPVOID(pSampleGrabber));
    if (FAILED(result))
        goto abort;

    result = pSampleGrabber->SetBufferSamples(false);
    if (FAILED(result))
        goto abort;

    result = pSampleGrabber->SetOneShot(false);
    if (FAILED(result))
        goto abort;

    sgCallback = new SampleGCB;

    result = pSampleGrabber->SetCallback(sgCallback, 1);
    if (FAILED(result))
        goto abort;

    result = pGraph->QueryInterface(IID_IMediaControl, PPVOID(pControl));
    if (FAILED(result))
        goto abort;

    result = pGraph->QueryInterface(IID_IMediaFilter, PPVOID(pMediaFilter));
    if (FAILED(result))
        goto abort;

    pMediaFilter->SetSyncSource(NULL);
    pMediaFilter->Release();

    /* Seleccionar Mejor Resolucion */
    result = setupSize();
    if (FAILED(result))
        goto abort;

    /* Verificar si este dispositivo utiliza Crossbar */
    result = checkCrossbar();
    if (FAILED(result)) goto abort;

    /* Enchufar los Filters */
    result = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, NULL, pCap, pGrabber, nullRenderer);
    if (FAILED(result)) goto abort;

    /* Liberar el pBuild */
    pBuild->Release();
    pBuild = NULL;

    /* FIXME: buscar un mecanismo para enviar los cuadros */
#ifdef USE_QOBJECT
    const char *signal = SIGNAL(sendFrame(unsigned char*,int,int,int,int,uint));
    connect(sgCallback, signal, this, signal);
#endif
    /* Ejecutar el FilterGraph */
    pControl->Run();

    pGrabber->Release();
    nullRenderer->Release();

    pGrabber     = NULL;
    nullRenderer = NULL;
    connected    = true;

    return;

abort:
    if (pMediaFilter)
        pMediaFilter->Release();
    cleanUp();
}

HRESULT
CPCapDevice::checkCrossbar()
{
    HRESULT     result      = E_FAIL;
    IAMCrossbar *pmCrossbar = NULL;

    result = pBuild->FindInterface(&LOOK_UPSTREAM_ONLY,
                                   0,
                                   pCap,
                                   IID_IAMCrossbar,
                                   PPVOID(pmCrossbar));
    if (SUCCEEDED(result)) {
        pmCrossbar->Release();

        pmCrossbar = NULL;
        result     = pBuild->FindInterface(&capture_mode,
                                           &MEDIATYPE_Interleaved,
                                           pCap,
                                           IID_IAMCrossbar,
                                           PPVOID(pmCrossbar));
        if (result != NOERROR) {
            result = pBuild->FindInterface(&capture_mode,
                                           &MEDIATYPE_Video,
                                           pCap,
                                           IID_IAMCrossbar,
                                           PPVOID(pmCrossbar));
        }
        /* Ahora si, a buscar lo que queremos! */
        if (SUCCEEDED(result)) {
            result = pmCrossbar->QueryInterface(IID_IBaseFilter,
                                                PPVOID(pCrossbar));
            if (SUCCEEDED(result)) {
                TCHAR filterName[128] = {'\0'};

                swprintf_s(filterName, 128, L"%s_CrossbarFilter", deviceName);
                result = pGraph->AddFilter(pCrossbar, filterName);
                if (SUCCEEDED(result)) {
                    /* FIXME: Agregar capacidad de seleccion de conexion
                     *        fisica.
                     */
                    result = route_crossbar(pmCrossbar,
                                            physical_connection);
                    if (FAILED(result))
                        goto abort;
                }
            }
            pmCrossbar->Release();
            return S_OK;
        } else
            goto abort;
    }
    /* Esto quiere decir, no es necesario Crossbar, lo cual esta bien */
    pCrossbar           = NULL;
    physical_connection = -1;

    return S_OK;

abort:
    if (pmCrossbar)
        pmCrossbar->Release();
    pCrossbar           = NULL;
    pmCrossbar          = NULL;
    physical_connection = -1;

    return E_FAIL;
}

HRESULT
CPCapDevice::setupSize()
{
    /* FIXME: deberiamos poder elegir la resolucion */
    HRESULT                   result  = E_FAIL;
    IAMStreamConfig          *pConfig = NULL;
    AM_MEDIA_TYPE            *pmt     = NULL;
    VIDEO_STREAM_CONFIG_CAPS  scc;

    result = pBuild->FindInterface(&PIN_CATEGORY_CAPTURE,
                                   0,
                                   pCap,
                                   IID_IAMStreamConfig,
                                   PPVOID(pConfig));

    if (SUCCEEDED(result)) {
        long llargestArea  = 0;
        int  iPreferredFmt = 0;
        int  iCount        = 0;
        int  iSize         = 0;
        long lSWidth       = 0;
        long lSHeight      = 0;

        result = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
        if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS)) {
            for (int iFormat = 0 ; iFormat < iCount ; iFormat++) {
                result = pConfig->GetStreamCaps(iFormat, &pmt, (BYTE *)&scc);
                if (SUCCEEDED(result)) {
                    lSWidth  = scc.MaxOutputSize.cx;
                    lSHeight = scc.MaxOutputSize.cy;
                    if (llargestArea < lSWidth * lSHeight) {
                        llargestArea  = lSWidth * lSHeight;
                        iPreferredFmt = iFormat;
                    }
                    CoTaskMemFree(pmt);
                }
            }
            result = pConfig->GetStreamCaps(iPreferredFmt, &pmt, (BYTE *)&scc);
            if (SUCCEEDED(result)) {
                VIDEOINFOHEADER *vih;
                vih = reinterpret_cast<VIDEOINFOHEADER *>(pmt->pbFormat);
                if (vih) {
                    vih->bmiHeader.biWidth  = lSWidth;
                    vih->bmiHeader.biHeight = lSHeight;

                    width  = vih->bmiHeader.biWidth;
                    height = vih->bmiHeader.biHeight;
                }
                result = pConfig->SetFormat(pmt);
                if (FAILED(result))
                    goto abort;

                pmt->subtype = MEDIASUBTYPE_RGB24;

                result = pSampleGrabber->SetMediaType(pmt);
                if (FAILED(result))
                    goto abort;

                sgCallback->setSize(width, height);
                CoTaskMemFree(vih);
            }
        }
        pConfig->Release();
    }
    return result;

abort:
    pConfig->Release();
    return E_FAIL;
}

HRESULT
CPCapDevice::getDeviceFilter()
{
    HRESULT        result        = E_FAIL;
    TCHAR          fStr[128]     = {'\0'};
    ICreateDevEnum *pDevEnum     = NULL;
    IEnumMoniker   *pEnumMoniker = NULL;
    IMoniker       *pMoniker     = NULL;
    IPropertyBag   *pBag         = NULL;
    bool           found         = false;

    VARIANT        vStr;

    result = CoCreateInstance(CLSID_SystemDeviceEnum,
                              0,
                              CLSCTX_INPROC_SERVER,
                              IID_ICreateDevEnum,
                              PPVOID(pDevEnum));
    if (FAILED(result))
        goto abort;

    result = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
             &pEnumMoniker,
             0);
    if (FAILED(result))
        goto abort;

    if (!pEnumMoniker)
        goto abort;

    while (pEnumMoniker->Next(1, &pMoniker, 0) == S_OK && !found) {
        result = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, PPVOID(pBag));
        if (FAILED(result))
            goto abort;

        VariantInit(&vStr);

        vStr.vt = VT_BSTR;
        result  = pBag->Read(L"DevicePath", &vStr, 0);

        if (FAILED(result))
            goto abort;

        if (wcscmp(vStr.bstrVal, deviceID)) {
            found = true;

            swprintf_s(fStr, 128, L"%s_CaptureFilter", deviceName);

            if (pCap)
                pCap->Release();

            pCap   = NULL;
            result = pMoniker->BindToObject(0,
                                            0,
                                            IID_IBaseFilter,
                                            PPVOID(pCap));
            if (FAILED(result))
                goto abort;

            result = pGraph->AddFilter(pCap, fStr);
            if (FAILED(result))
                goto abort;
        }

        VariantClear(&vStr);
        pMoniker->Release();

        pMoniker = NULL;
    }
    pEnumMoniker->Release();
    pDevEnum->Release();

    if (!found) {
        result = E_FAIL;
        pCap   = NULL;
    } else {
        result = S_OK;
    }

    return result;

abort:
    if (pDevEnum)
        pDevEnum->Release();
    if (pEnumMoniker)
        pEnumMoniker->Release();
    if (pMoniker)
        pMoniker->Release();
    if (pBag)
        pBag->Release();

    pDevEnum     = NULL;
    pEnumMoniker = NULL;
    pMoniker     = NULL;
    pBag         = NULL;

    return result;
}

void
CPCapDevice::negate()
{
    sgCallback->negate();
}

#else

CPCapCaptureThread::CPCapCaptureThread(CPCapDevice *device, QObject *parent) :
    QThread(parent),
    m_Device(device),
    m_Terminated(false)
{
}

void
CPCapCaptureThread::prepareTermination()
{
    m_Terminated = true;
}

void
CPCapCaptureThread::run()
{
    if (!m_Device)
        return;

    while (m_Device && m_Device->isConnected() && !m_Terminated) {
        m_Device->emitFrame();
        usleep(41600);
    }
    exec();
    return;
}

CPCapDevice::CPCapDevice(const char *uid,
                         const char *name,
                         int physical,
                         int w,
                         int h,
                         int fmt,
                         QObject *parent) :
    QObject(parent),
    width(w),
    height(h),
    stride(0),
    fourcc(fmt),
    connected(false),
    physical_connection(physical),
    formats(NULL),
    m_Thread(new CPCapCaptureThread(this, this)),
    deviceID(NULL),
    deviceName(NULL),
    fd(-1),
    inverted(false),
    buffers(NULL),
    formats_count(0),
    n_buffers(0),
    mode(-1)
{
    deviceID   = mstrdup(uid);
    deviceName = mstrdup(name);
}


CPCapDevice::CPCapDevice(const char *uid, const char *name, int physical, QObject *parent) :
    QObject(parent),
    width(0),
    height(0),
    stride(0),
    fourcc(V4L2_PIX_FMT_RGB24),
    connected(false),
    physical_connection(physical),
    formats(NULL),
    m_Thread(new CPCapCaptureThread(this, this)),
    deviceID(NULL),
    deviceName(NULL),
    fd(-1),
    inverted(false),
    buffers(NULL),
    formats_count(0),
    n_buffers(0),
    mode(-1)
{
    deviceID   = mstrdup(uid);
    deviceName = mstrdup(name);
}

CPCapDevice::~CPCapDevice()
{
    disconnectDevice();
    reset();
}

void
CPCapDevice::reset()
{
    if (mode != V4L2_CAP_STREAMING && buffers && buffers[0].data) {
        free(buffers[0].data);
    } else {
        for (unsigned int i = 0 ; i < n_buffers ; i++) {
            if (buffers[i].data) {
                munmap(buffers[i].data, buffers[i].length);
                buffers[i].data = NULL;
            }
        }
    }

    if (buffers)
        free(buffers);

    if (formats)
        free(formats);

    buffers = NULL;
    formats = NULL;

    if (deviceID)
        delete [] deviceID;

    if (deviceName)
        delete[] deviceName;

    if (fd != -1)
        close(fd);
}

bool
CPCapDevice::initializeBuffers(unsigned int size)
{
    switch (mode) {
    case V4L2_CAP_READWRITE:
        n_buffers         = 1;
        buffers           = (buffer_t *)malloc(sizeof(buffer_t));
        buffers[0].data   = (uint8_t *)malloc(size);
        buffers[0].length = size;

        return true;
        break;
    case V4L2_CAP_STREAMING: {
        int                         type;
        struct v4l2_requestbuffers  reqbuf;

        /* TODO: verificar por que se puden tener muchos BUFFERS? */
        reqbuf.count  = 1;
        reqbuf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        reqbuf.memory = V4L2_MEMORY_MMAP;

        if (xioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1)
            return false;

        if (reqbuf.count == 0)
            return false;

        n_buffers = reqbuf.count;
        buffers   = (buffer_t *)malloc(n_buffers * sizeof(buffer_t));

        if (!buffers)
            return false;

        memset(buffers, 0, n_buffers * sizeof(buffer_t));

        for (unsigned int i = 0 ; i < n_buffers ; i++) {
            struct v4l2_buffer  buf;

            memset(&buf, 0, sizeof(buf));

            buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index  = i;

            if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
                n_buffers = 0;
                for (int j = i ; j >= 0 ; j--)
                    munmap(buffers[j].data, buffers[j].length);

                return false;
            }
            buffers[i].length = buf.length;
            buffers[i].data   = mmap(NULL,
                                     buf.length,
                                     PROT_READ | PROT_WRITE,
                                     MAP_SHARED,
                                     fd,
                                     buf.m.offset);

            if (buffers[i].data == MAP_FAILED) {
                n_buffers = 0;

                for (int j = i - 1 ; j >= 0 ; j--)
                    munmap(buffers[j].data, buffers[j].length);

                return false;
            }
        }

        for (unsigned int i = 0 ; i < n_buffers ; i++) {
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(buf));

            buf.type   = reqbuf.type;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index  = i;

            if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
                n_buffers = 0;

                for (int j = i ; j > 0 ; j--)
                    munmap(buffers[j].data, buffers[j].length);

                return false;
            }
        }
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        if (xioctl (fd, VIDIOC_STREAMON, &type) == -1)
            return false;

        return true;
    }
    break;
    default:
        return false;
        break;
    }
    return true;
}

bool
CPCapDevice::openDevice()
{
    /* no se ha establecido */
    if (!deviceID)
        return false;

    struct stat             st;
    struct v4l2_capability  cap;
    struct v4l2_format      fmt;
    PhysicalInput           *inputs     = NULL;
    unsigned int            input_count = 0;
#ifdef DEBUG
    fprintf(stderr, "\t** Inicializando %s\n\n", deviceName);
#endif
    /* No existe */
    if (stat(deviceID, &st) == -1)
        return false;

    /* No es dispositivo de caracteres */
    if (!S_ISCHR(st.st_mode))
        return false;

    /* Abrir */
    fd = open(deviceID, O_RDONLY, 0);

    if (fd == -1)
        return false;

    /* Formatos */
    if (!enumFormats())
        return false;

    /* Modo : STREAMING | READWRITE */
    if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
        return false;

    if (cap.capabilities & V4L2_CAP_STREAMING) {
        mode = V4L2_CAP_STREAMING;
    } else if (cap.capabilities & V4L2_CAP_READWRITE) {
        mode = V4L2_CAP_READWRITE;
    }

#ifdef DEBUG
    debug_dump_formats(formats, formats_count);
#endif

    /* Conexion Fisica */
    if (!enumInputs(&inputs, &input_count))
        return false;
#ifdef DEBUG
    debug_dump_inputs(inputs, input_count, physical_connection);
#endif
    if (physical_connection == -1) {
        if (input_count == 1)
            physical_connection = inputs[0].index;
        else if (input_count > 1 && physical_connection == -1) {
            /*
             * FIXME: hay que avisar los tipos de conexion,
             *        pero ademas debe existir un modo para
             *        solicitar los tipos existentes.
             */
        }
    }
    free (inputs);

    if (xioctl(fd, VIDIOC_S_INPUT, &physical_connection) == -1)
        return false;

    /* Done Listing Formats */
    memset(&fmt, 0, sizeof(fmt));

    /* FIXME: ser capaces de seleccionar el formato. */
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = width;
    fmt.fmt.pix.height      = height;
    fmt.fmt.pix.pixelformat = fourcc;
    fmt.fmt.pix.field       = V4L2_FIELD_ANY;

    if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
        return false;
#ifdef DEBUG
    debug_dump_format(fmt);
#endif
    width  = fmt.fmt.pix.width;
    height = fmt.fmt.pix.height;
    stride = fmt.fmt.pix.bytesperline;
    fourcc = fmt.fmt.pix.pixelformat;

    return initializeBuffers(fmt.fmt.pix.sizeimage);
}

int
CPCapDevice::startCapture()
{
    m_Thread->start();
    return m_Thread->isRunning();
}

void
CPCapDevice::connectDevice()
{
    /* Open Device */
    if (!openDevice())
        connected = false;
    else
        connected = startCapture();
}

void
CPCapDevice::negate()
{
    inverted = !inverted;
}

void
CPCapDevice::emitFrame()
{
    if (fd == -1 || !connected || !m_Mutex.tryLock())
        return;

    switch (mode) {
    case V4L2_CAP_STREAMING: {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));

        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;

        if (xioctl(fd, VIDIOC_DQBUF, &buf) != -1) {
            if (buf.index >= n_buffers)
                return;

            xioctl(fd, VIDIOC_QBUF, &buf);
        } else {
            xioctl(fd, VIDIOC_QBUF, &buf);
            return;
        }
    }
    break;
    case V4L2_CAP_READWRITE:
        if (read(fd, buffers[0].data, buffers[0].length) == -1)
            return;
        break;
    }
    /* FIXME: enviar los datos para mostrarlos... */
#ifdef USE_QOBJECT
    emit sendFrame((unsigned char *)buffers[0].data,
                   width,
                   height,
                   stride,
                   fourcc,
                   buffers[0].length);
    m_Mutex.unlock();
#else
#warning "No se ha implementado un metodo para enviar los datos."
#endif
}

bool
CPCapDevice::enumInputs(PhysicalInput **inputs, unsigned int *count)
{
    int                index;
    struct v4l2_input  found_inputs;

    memset(&found_inputs, 0, sizeof(found_inputs));

    found_inputs.index = 0;
    *inputs            = NULL;

    while (xioctl(fd, VIDIOC_ENUMINPUT, &found_inputs) == 0) {
        void *aux;

        index  = found_inputs.index;
        *count = index + 1;
        aux    = realloc(*inputs, (*count) * sizeof(PhysicalInput));
        if (aux) {
            *inputs = (PhysicalInput *)aux;

            (*inputs)[index].index = index;
            memcpy((*inputs)[index].name, found_inputs.name, 32);
        }
        found_inputs.index++;
    }

    return true;
}

bool
CPCapDevice::enumFormats()
{
    struct v4l2_fmtdesc     fmt;
    unsigned int            index;
    __u32                   t_formats[64]       = {0};
    __u8                    description[32][64] = {{0}};

    memset(&fmt, 0, sizeof(fmt));

    fmt.index = 0;
    fmt.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    while (xioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
        t_formats[fmt.index] = fmt.pixelformat;

        memcpy(description[fmt.index], fmt.description, 32);
        fmt.index++;
    }
    formats_count = fmt.index;

    formats = (FormatsInfo *)malloc(formats_count * sizeof(FormatsInfo));
    for (index = 0 ; index < formats_count ; index++) {
        struct v4l2_frmsizeenum fsize;

        memset(&fsize, 0, sizeof(fsize));

        fsize.index                 = 0;
        fsize.pixel_format          = t_formats[index];
        formats[index].fmt          = fsize.pixel_format;
        formats[index].sizes_count  = 0;
        /* copiar descripcion */
        memcpy(formats[index].description, description[index], 32);

        /* iniciar enumeracion */
        while (xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize) == 0) {
            struct FormatSize frmsize;
            int               width   = 0;
            int               height  = 0;

            switch (fsize.type) {
            case V4L2_FRMSIZE_TYPE_DISCRETE:
                width  = fsize.discrete.width;
                height = fsize.discrete.height;
                break;
            case V4L2_FRMSIZE_TYPE_CONTINUOUS:
                width  = fsize.stepwise.max_width;
                height = fsize.stepwise.max_height;
                break;
            case V4L2_FRMSIZE_TYPE_STEPWISE:
                width  = fsize.stepwise.max_width;
                height = fsize.stepwise.max_height;
                break;
            }
            struct v4l2_frmivalenum ivalenum;
            memset(&ivalenum, 0, sizeof(ivalenum));

            ivalenum.index        = 0;
            ivalenum.pixel_format = fsize.pixel_format;
            ivalenum.width        = width;
            ivalenum.height       = height;
            while (xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &ivalenum) == 0) {
                __u32 numerator   = 0;
                __u32 denominator = 0;
                switch (ivalenum.type) {
                case V4L2_FRMIVAL_TYPE_DISCRETE:
                    numerator   = ivalenum.discrete.numerator;
                    denominator = ivalenum.discrete.denominator;
                    break;
                case V4L2_FRMIVAL_TYPE_CONTINUOUS:
                    numerator   = ivalenum.stepwise.min.numerator;
                    denominator = ivalenum.stepwise.min.denominator;
                    break;
                case V4L2_FRMIVAL_TYPE_STEPWISE:
                    numerator   = ivalenum.stepwise.min.numerator;
                    denominator = ivalenum.stepwise.min.denominator;
                    break;

                }
                frmsize.fps_denominator[ivalenum.index] = denominator;
                frmsize.fps_numerator[ivalenum.index]   = numerator;

                ivalenum.index++;
            }
            frmsize.fps_count                 = ivalenum.index;
            frmsize.width                     = width;
            frmsize.height                    = height;
            formats[index].sizes[fsize.index] = frmsize;
            fsize.index++;
        }
        formats[index].sizes_count = fsize.index;
    }
    formats->count = formats_count;

    if (formats_count == 0)
        return false;

    return true;
}
#endif

bool
CPCapDevice::isConnected() const
{
    return connected;
}

long
CPCapDevice::getCaptureProperty(long property, long *value) const
{
    long result = 0;
#ifdef WIN32
    long flag;

    result = E_FAIL;
    if (property == CPCAP_VideoPropertyExposure) {
        IAMCameraControl *ctl;

        ctl  = NULL;
        flag = CameraControl_Flags_Manual;

        pCap->QueryInterface(IID_IAMCameraControl, PPVOID(ctl));
        if (ctl) {
            result = ctl->Get(CameraControl_Exposure, value, &flag);
            ctl->Release();
        }
    } else {
        IAMVideoProcAmp *ctl;

        ctl  = NULL;
        flag = VideoProcAmp_Flags_Manual;

        pCap->QueryInterface(IID_IAMVideoProcAmp, PPVOID(ctl));
        if (ctl) {
            result = ctl->Get(property, value, &flag);
            ctl->Release();
        }
    }
#endif
#ifdef LINUX
    struct v4l2_queryctrl query;
    struct v4l2_control   ctl;

    memset(&query, 0, sizeof(query));

    result   = 0;
    query.id = property;

    if (xioctl(fd, VIDIOC_QUERYCTRL, &query) == -1)
        return 1;

    if (query.flags & V4L2_CTRL_FLAG_DISABLED)
        return 1;

    memset(&ctl, 0, sizeof(ctl));

    ctl.id = property;

    if (xioctl(fd, VIDIOC_G_CTRL, &ctl) == -1)
        return 1;

    *value = ctl.value;
#endif

    return result;
}

long
CPCapDevice::setCaptureProperty(long property, long value)
{
    long result = 0;
#ifdef WIN32
    long flag;

    result = E_FAIL;
    if (property == CPCAP_VideoPropertyExposure) {
        IAMCameraControl *ctl;

        ctl  = NULL;
        flag = CameraControl_Flags_Manual;

        pCap->QueryInterface(IID_IAMCameraControl, PPVOID(ctl));
        if (ctl) {
            result = ctl->Set(CameraControl_Exposure, value, flag);
            ctl->Release();
        }
    } else {
        IAMVideoProcAmp *ctl;

        ctl  = NULL;
        flag = VideoProcAmp_Flags_Manual;

        pCap->QueryInterface(IID_IAMVideoProcAmp, PPVOID(ctl));
        if (ctl) {
            result = ctl->Set(property, value, flag);
            ctl->Release();
        }
    }
#endif
#ifdef LINUX
    struct v4l2_control   ctl;

    memset(&ctl, 0, sizeof(ctl));

    result    = 0;
    ctl.id    = property;
    ctl.value = value;

    if (xioctl(fd, VIDIOC_S_CTRL, &ctl) == -1)
        return 1;
#endif
    return result;
}

long
CPCapDevice::getCapturePropertyRange(long property,
                                     long *pMin,
                                     long *pMax,
                                     long *pSteppingDelta,
                                     long *pDefault) const
{
    long result = 0;
#ifdef WIN32
    long flag;

    result = E_FAIL;
    if (property == CPCAP_VideoPropertyExposure) {
        IAMCameraControl *ctl;

        ctl  = NULL;
        flag = CameraControl_Flags_Manual;

        pCap->QueryInterface(IID_IAMCameraControl, PPVOID(ctl));
        if (ctl) {
            /* FIXME : en realidad no funciona. */
            result = ctl->GetRange(CameraControl_Exposure,
                                   pMin,
                                   pMax,
                                   pSteppingDelta,
                                   pDefault,
                                   &flag);
            ctl->Release();
        }
    } else {
        IAMVideoProcAmp *ctl;

        ctl  = NULL;
        flag = VideoProcAmp_Flags_Manual;

        pCap->QueryInterface(IID_IAMVideoProcAmp, PPVOID(ctl));
        if (ctl) {
            result = ctl->GetRange(property,
                                   pMin,
                                   pMax,
                                   pSteppingDelta,
                                   pDefault,
                                   &flag);
            ctl->Release();
        }
    }
#endif
#ifdef LINUX
    struct v4l2_queryctrl query;

    result          = 0;
    *pMin           = 0;
    *pMax           = 0;
    *pSteppingDelta = 0;
    *pDefault       = 0;

    memset(&query, 0, sizeof(query));

    query.id = property;

    if (xioctl(fd, VIDIOC_QUERYCTRL, &query) == -1)
        return 1;

    if (query.flags & V4L2_CTRL_FLAG_DISABLED)
        return 1;

    *pMin           = query.minimum;
    *pMax           = query.maximum;
    *pSteppingDelta = query.step;
    *pDefault       = query.default_value;
#endif
    return result;
}

void
CPCapDevice::setBrightness(long value)
{
    setCaptureProperty(CPCAP_VideoPropertyBrightness, value);
}

void
CPCapDevice::setContrast(long value)
{
    setCaptureProperty(CPCAP_VideoPropertyContrast, value);
}

void
CPCapDevice::setHue(long value)
{
    setCaptureProperty(CPCAP_VideoPropertyHue, value);
}

void
CPCapDevice::setSharpness(long value)
{
    setCaptureProperty(CPCAP_VideoPropertySharpness, value);
}

void
CPCapDevice::setSaturation(long value)
{
    setCaptureProperty(CPCAP_VideoPropertySaturation, value);
}

void
CPCapDevice::setGamma(long value)
{
    setCaptureProperty(CPCAP_VideoPropertyGamma, value);
}

void
CPCapDevice::setGain(long value)
{
    setCaptureProperty(CPCAP_VideoPropertyGain, value);
}

void
CPCapDevice::setExposure(long value)
{
    setCaptureProperty(CPCAP_VideoPropertyExposure, value);
}

long
CPCapDevice::getBrightness() const
{
    long value;

    getCaptureProperty(CPCAP_VideoPropertyBrightness, &value);

    return value;
}

long
CPCapDevice::getContrast() const
{
    long value;

    getCaptureProperty(CPCAP_VideoPropertyContrast, &value);

    return value;
}

long
CPCapDevice::getHue() const
{
    long value;

    getCaptureProperty(CPCAP_VideoPropertyHue, &value);

    return value;
}

long
CPCapDevice::getSaturation() const
{
    long value;

    getCaptureProperty(CPCAP_VideoPropertySaturation, &value);

    return value;

}

long
CPCapDevice::getSharpness() const
{
    long value;

    getCaptureProperty(CPCAP_VideoPropertySharpness, &value);

    return value;
}

long
CPCapDevice::getGamma() const
{
    long value;

    getCaptureProperty(CPCAP_VideoPropertyGamma, &value);

    return value;
}

int
CPCapDevice::nativeWidth() const
{
    return width;
}

int
CPCapDevice::nativeHeight() const
{
    return height;
}

double
CPCapDevice::getFPS() const
{
    double fps;
    fps = 30.0;
#ifdef LINUX
    struct v4l2_frmivalenum frmivalenum;
    memset(&frmivalenum, 0, sizeof(frmivalenum));
    xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum);
#endif
#ifdef WIN32
    fps = sgCallback->getFPS();
#endif
    return fps;
}

int
CPCapDevice::getPhysicalConnection() const
{
    return physical_connection;
}

void
CPCapDevice::disconnectDevice()
{
#ifdef Q_OS_LINUX
    m_Thread->prepareTermination();
    m_Thread->terminate();
    m_Thread->wait();
    int type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (xioctl(fd, VIDIOC_STREAMOFF, &type) == -1)
        fprintf(stderr, "ERROR: no se pudo parar la camara\n");
#endif
    connected = false;
}

const struct FormatsInfo *
CPCapDevice::listFormats() const {
    return const_cast<struct FormatsInfo *>(formats);
}

#define DUMP_FOURCC(x) (x) & 0xFF, ((x) >> 8) & 0xFF, ((x) >> 16) & 0xFF, ((x) >> 24) & 0xFF
void
CPCapDevice::setResolution(int)
{
#ifdef LINUX
#endif
}
